/** @file

  Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Flash.h"

NAND_PART_INFO_TABLE gNandPartInfoTable[1] = {
  { 0x2C, 0xBA, 17, 11 }
};

NAND_FLASH_INFO *gNandFlashInfo = NULL;
UINT8           *gEccCode;
UINTN           gNum512BytesChunks = 0;

//

// Device path for SemiHosting. It contains our autogened Caller ID GUID.

//

typedef struct {

  VENDOR_DEVICE_PATH        Guid;

  EFI_DEVICE_PATH_PROTOCOL  End;

} FLASH_DEVICE_PATH;



FLASH_DEVICE_PATH gDevicePath = {
  {
    { HARDWARE_DEVICE_PATH, HW_VENDOR_DP, { sizeof (VENDOR_DEVICE_PATH), 0 } },
    EFI_CALLER_ID_GUID
  },
  { END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0} }
};



//Actual page address = Column address + Page address + Block address.
UINTN
GetActualPageAddressInBytes (
  UINTN BlockIndex,
  UINTN PageIndex
)
{
  //BlockAddressStart = Start of the Block address in actual NAND
  //PageAddressStart = Start of the Page address in actual NAND
  return ((BlockIndex << gNandFlashInfo->BlockAddressStart) + (PageIndex << gNandFlashInfo->PageAddressStart));
}

VOID
NandSendCommand (
  UINT8 Command
)
{
  MmioWrite16(GPMC_NAND_COMMAND_0, Command);
}

VOID
NandSendAddress (
  UINT8 Address
)
{
  MmioWrite16(GPMC_NAND_ADDRESS_0, Address);
}

UINT16
NandReadStatus (
  VOID
  )
{
  //Send READ STATUS command
  NandSendCommand(READ_STATUS_CMD);

  //Read status.
  return MmioRead16(GPMC_NAND_DATA_0);
}

VOID
NandSendAddressCycles (
  UINTN Address
)
{
  //Column address
  NandSendAddress(Address & 0xff);
  Address >>= 8;

  //Column address
  NandSendAddress(Address & 0x07);
  Address >>= 3;

  //Page and Block address
  NandSendAddress(Address & 0xff);
  Address >>= 8;

  //Block address
  NandSendAddress(Address & 0xff);
  Address >>= 8;

  //Block address
  NandSendAddress(Address & 0x01);
}

VOID
GpmcInit (
  VOID
  )
{
  //Enable Smart-idle mode.
  MmioWrite32 (GPMC_SYSCONFIG, SMARTIDLEMODE);

  //Set IRQSTATUS and IRQENABLE to the reset value
  MmioWrite32 (GPMC_IRQSTATUS, 0x0);
  MmioWrite32 (GPMC_IRQENABLE, 0x0);

  //Disable GPMC timeout control.
  MmioWrite32 (GPMC_TIMEOUT_CONTROL, TIMEOUTDISABLE);

  //Set WRITEPROTECT bit to enable write access.
  MmioWrite32 (GPMC_CONFIG, WRITEPROTECT_HIGH);

  //NOTE: Following GPMC_CONFIGi_0 register settings are taken from u-boot memory dump.
  MmioWrite32 (GPMC_CONFIG1_0, DEVICETYPE_NAND | DEVICESIZE_X16);
  MmioWrite32 (GPMC_CONFIG2_0, CSRDOFFTIME | CSWROFFTIME);
  MmioWrite32 (GPMC_CONFIG3_0, ADVRDOFFTIME | ADVWROFFTIME);
  MmioWrite32 (GPMC_CONFIG4_0, OEONTIME | OEOFFTIME | WEONTIME | WEOFFTIME);
  MmioWrite32 (GPMC_CONFIG5_0, RDCYCLETIME | WRCYCLETIME | RDACCESSTIME | PAGEBURSTACCESSTIME);
  MmioWrite32 (GPMC_CONFIG6_0, WRACCESSTIME | WRDATAONADMUXBUS | CYCLE2CYCLEDELAY | CYCLE2CYCLESAMECSEN);
  MmioWrite32 (GPMC_CONFIG7_0, MASKADDRESS_128MB | CSVALID | BASEADDRESS);
}

EFI_STATUS
NandDetectPart (
  VOID
)
{
  UINT8      NandInfo = 0;
  UINT8      PartInfo[5];
  UINTN      Index;
  BOOLEAN    Found = FALSE;

  //Send READ ID command
  NandSendCommand(READ_ID_CMD);

  //Send one address cycle.
  NandSendAddress(0);

  //Read 5-bytes to idenfity code programmed into the NAND flash devices.
  //BYTE 0 = Manufacture ID
  //Byte 1 = Device ID
  //Byte 2, 3, 4 = Nand part specific information (Page size, Block size etc)
  for (Index = 0; Index < sizeof(PartInfo); Index++) {
    PartInfo[Index] = MmioRead16(GPMC_NAND_DATA_0);
  }

  //Check if the ManufactureId and DeviceId are part of the currently supported nand parts.
  for (Index = 0; Index < sizeof(gNandPartInfoTable)/sizeof(NAND_PART_INFO_TABLE); Index++) {
    if (gNandPartInfoTable[Index].ManufactureId == PartInfo[0] && gNandPartInfoTable[Index].DeviceId == PartInfo[1]) {
      gNandFlashInfo->BlockAddressStart = gNandPartInfoTable[Index].BlockAddressStart;
      gNandFlashInfo->PageAddressStart = gNandPartInfoTable[Index].PageAddressStart;
      Found = TRUE;
      break;
    }
  }

  if (Found == FALSE) {
    DEBUG ((EFI_D_ERROR, "Nand part is not currently supported. Manufacture id: %x, Device id: %x\n", PartInfo[0], PartInfo[1]));
    return EFI_NOT_FOUND;
  }

  //Populate NAND_FLASH_INFO based on the result of READ ID command.
  gNandFlashInfo->ManufactureId = PartInfo[0];
  gNandFlashInfo->DeviceId = PartInfo[1];
  NandInfo = PartInfo[3];

  if (PAGE_SIZE(NandInfo) == PAGE_SIZE_2K_VAL) {
    gNandFlashInfo->PageSize = PAGE_SIZE_2K;
  } else {
    DEBUG ((EFI_D_ERROR, "Unknown Page size.\n"));
    return EFI_DEVICE_ERROR;
  }

  if (SPARE_AREA_SIZE(NandInfo) == SPARE_AREA_SIZE_64B_VAL) {
    gNandFlashInfo->SparePageSize = SPARE_AREA_SIZE_64B;
  } else {
    DEBUG ((EFI_D_ERROR, "Unknown Spare area size.\n"));
    return EFI_DEVICE_ERROR;
  }

  if (BLOCK_SIZE(NandInfo) == BLOCK_SIZE_128K_VAL) {
    gNandFlashInfo->BlockSize = BLOCK_SIZE_128K;
  } else {
    DEBUG ((EFI_D_ERROR, "Unknown Block size.\n"));
    return EFI_DEVICE_ERROR;
  }

  if (ORGANIZATION(NandInfo) == ORGANIZATION_X8) {
    gNandFlashInfo->Organization = 0;
  } else if (ORGANIZATION(NandInfo) == ORGANIZATION_X16) {
    gNandFlashInfo->Organization = 1;
  }

  //Calculate total number of blocks.
  gNandFlashInfo->NumPagesPerBlock = DivU64x32(gNandFlashInfo->BlockSize, gNandFlashInfo->PageSize);

  return EFI_SUCCESS;
}

VOID
NandConfigureEcc (
  VOID
  )
{
  //Define ECC size 0 and size 1 to 512 bytes
  MmioWrite32 (GPMC_ECC_SIZE_CONFIG, (ECCSIZE0_512BYTES | ECCSIZE1_512BYTES));
}

VOID
NandEnableEcc (
  VOID
  )
{
  //Clear all the ECC result registers and select ECC result register 1
  MmioWrite32 (GPMC_ECC_CONTROL, (ECCCLEAR | ECCPOINTER_REG1));

  //Enable ECC engine on CS0
  MmioWrite32 (GPMC_ECC_CONFIG, (ECCENABLE | ECCCS_0 | ECC16B));
}

VOID
NandDisableEcc (
  VOID
  )
{
  //Turn off ECC engine.
  MmioWrite32 (GPMC_ECC_CONFIG, ECCDISABLE);
}

VOID
NandCalculateEcc (
  VOID
  )
{
  UINTN Index;
  UINTN EccResultRegister;
  UINTN EccResult;

  //Capture 32-bit ECC result for each 512-bytes chunk.
  //In our case PageSize is 2K so read ECC1-ECC4 result registers and
  //generate total of 12-bytes of ECC code for the particular page.

  EccResultRegister = GPMC_ECC1_RESULT;

  for (Index = 0; Index < gNum512BytesChunks; Index++) {

    EccResult = MmioRead32 (EccResultRegister);

    //Calculate ECC code from 32-bit ECC result value.
    //NOTE: Following calculation is not part of TRM. We got this information
    //from Beagleboard mailing list.
    gEccCode[Index * 3] = EccResult & 0xFF;
    gEccCode[(Index * 3) + 1] = (EccResult >> 16) & 0xFF;
    gEccCode[(Index * 3) + 2] = (((EccResult >> 20) & 0xF0) | ((EccResult >> 8) & 0x0F));

    //Point to next ECC result register.
    EccResultRegister += 4;
  }
}

EFI_STATUS
NandReadPage (
  IN  UINTN                         BlockIndex,
  IN  UINTN                         PageIndex,
  OUT VOID                          *Buffer,
  OUT UINT8                         *SpareBuffer
)
{
  UINTN      Address;
  UINTN      Index;
  UINTN      NumMainAreaWords = (gNandFlashInfo->PageSize/2);
  UINTN      NumSpareAreaWords = (gNandFlashInfo->SparePageSize/2);
  UINT16     *MainAreaWordBuffer = Buffer;
  UINT16     *SpareAreaWordBuffer = (UINT16 *)SpareBuffer;
  UINTN      Timeout = MAX_RETRY_COUNT;

  //Generate device address in bytes to access specific block and page index
  Address = GetActualPageAddressInBytes(BlockIndex, PageIndex);

  //Send READ command
  NandSendCommand(PAGE_READ_CMD);

  //Send 5 Address cycles to access specific device address
  NandSendAddressCycles(Address);

  //Send READ CONFIRM command
  NandSendCommand(PAGE_READ_CONFIRM_CMD);

  //Poll till device is busy.
  while (Timeout) {
    if ((NandReadStatus() & NAND_READY) == NAND_READY) {
      break;
    }
    Timeout--;
  }

  if (Timeout == 0) {
    DEBUG ((EFI_D_ERROR, "Read page timed out.\n"));
    return EFI_TIMEOUT;
  }

  //Reissue READ command
  NandSendCommand(PAGE_READ_CMD);

  //Enable ECC engine.
  NandEnableEcc();

  //Read data into the buffer.
  for (Index = 0; Index < NumMainAreaWords; Index++) {
    *MainAreaWordBuffer++ = MmioRead16(GPMC_NAND_DATA_0);
  }

  //Read spare area into the buffer.
  for (Index = 0; Index < NumSpareAreaWords; Index++) {
    *SpareAreaWordBuffer++ = MmioRead16(GPMC_NAND_DATA_0);
  }

  //Calculate ECC.
  NandCalculateEcc();

  //Turn off ECC engine.
  NandDisableEcc();

  //Perform ECC correction.
  //Need to implement..

  return EFI_SUCCESS;
}

EFI_STATUS
NandWritePage (
  IN  UINTN                         BlockIndex,
  IN  UINTN                         PageIndex,
  OUT VOID                          *Buffer,
  IN  UINT8                         *SpareBuffer
)
{
  UINTN      Address;
  UINT16     *MainAreaWordBuffer = Buffer;
  UINT16     *SpareAreaWordBuffer = (UINT16 *)SpareBuffer;
  UINTN      Index;
  UINTN      NandStatus;
  UINTN      Timeout = MAX_RETRY_COUNT;

  //Generate device address in bytes to access specific block and page index
  Address = GetActualPageAddressInBytes(BlockIndex, PageIndex);

  //Send SERIAL DATA INPUT command
  NandSendCommand(PROGRAM_PAGE_CMD);

  //Send 5 Address cycles to access specific device address
  NandSendAddressCycles(Address);

  //Enable ECC engine.
  NandEnableEcc();

  //Data input from Buffer
  for (Index = 0; Index < (gNandFlashInfo->PageSize/2); Index++) {
    MmioWrite16(GPMC_NAND_DATA_0, *MainAreaWordBuffer++);

    //After each write access, device has to wait to accept data.
    //Currently we may not be programming proper timing parameters to
    //the GPMC_CONFIGi_0 registers and we would need to figure that out.
    //Without following delay, page programming fails.
    gBS->Stall(1);
  }

  //Calculate ECC.
  NandCalculateEcc();

  //Turn off ECC engine.
  NandDisableEcc();

  //Prepare Spare area buffer with ECC codes.
  SetMem(SpareBuffer, gNandFlashInfo->SparePageSize, 0xFF);
  CopyMem(&SpareBuffer[ECC_POSITION], gEccCode, gNum512BytesChunks * 3);

  //Program spare area with calculated ECC.
  for (Index = 0; Index < (gNandFlashInfo->SparePageSize/2); Index++) {
    MmioWrite16(GPMC_NAND_DATA_0, *SpareAreaWordBuffer++);
  }

  //Send PROGRAM command
  NandSendCommand(PROGRAM_PAGE_CONFIRM_CMD);

  //Poll till device is busy.
  NandStatus = 0;
  while (Timeout) {
    NandStatus = NandReadStatus();
    if ((NandStatus & NAND_READY) == NAND_READY) {
      break;
    }
    Timeout--;
  }

  if (Timeout == 0) {
    DEBUG ((EFI_D_ERROR, "Program page timed out.\n"));
    return EFI_TIMEOUT;
  }

  //Bit0 indicates Pass/Fail status
  if (NandStatus & NAND_FAILURE) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
NandEraseBlock (
  IN UINTN BlockIndex
)
{
  UINTN      Address;
  UINTN      NandStatus;
  UINTN      Timeout = MAX_RETRY_COUNT;

  //Generate device address in bytes to access specific block and page index
  Address = GetActualPageAddressInBytes(BlockIndex, 0);

  //Send ERASE SETUP command
  NandSendCommand(BLOCK_ERASE_CMD);

  //Send 3 address cycles to device to access Page address and Block address
  Address >>= 11; //Ignore column addresses

  NandSendAddress(Address & 0xff);
  Address >>= 8;

  NandSendAddress(Address & 0xff);
  Address >>= 8;

  NandSendAddress(Address & 0xff);

  //Send ERASE CONFIRM command
  NandSendCommand(BLOCK_ERASE_CONFIRM_CMD);

  //Poll till device is busy.
  NandStatus = 0;
  while (Timeout) {
    NandStatus = NandReadStatus();
    if ((NandStatus & NAND_READY) == NAND_READY) {
      break;
    }
    Timeout--;
    gBS->Stall(1);
  }

  if (Timeout == 0) {
    DEBUG ((EFI_D_ERROR, "Erase block timed out for Block: %d.\n", BlockIndex));
    return EFI_TIMEOUT;
  }

  //Bit0 indicates Pass/Fail status
  if (NandStatus & NAND_FAILURE) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
NandReadBlock (
  IN UINTN                          StartBlockIndex,
  IN UINTN                          EndBlockIndex,
  OUT VOID                          *Buffer,
  OUT VOID                          *SpareBuffer
)
{
  UINTN      BlockIndex;
  UINTN      PageIndex;
  EFI_STATUS Status = EFI_SUCCESS;

  for (BlockIndex = StartBlockIndex; BlockIndex <= EndBlockIndex; BlockIndex++) {
    //For each block read number of pages
    for (PageIndex = 0; PageIndex < gNandFlashInfo->NumPagesPerBlock; PageIndex++) {
      Status = NandReadPage(BlockIndex, PageIndex, Buffer, SpareBuffer);
      if (EFI_ERROR(Status)) {
        return Status;
      }
      Buffer = ((UINT8 *)Buffer + gNandFlashInfo->PageSize);
    }
  }

  return Status;
}

EFI_STATUS
NandWriteBlock (
  IN UINTN                          StartBlockIndex,
  IN UINTN                          EndBlockIndex,
  OUT VOID                          *Buffer,
  OUT VOID                          *SpareBuffer
  )
{
  UINTN      BlockIndex;
  UINTN      PageIndex;
  EFI_STATUS Status = EFI_SUCCESS;

  for (BlockIndex = StartBlockIndex; BlockIndex <= EndBlockIndex; BlockIndex++) {
    //Page programming.
    for (PageIndex = 0; PageIndex < gNandFlashInfo->NumPagesPerBlock; PageIndex++) {
      Status = NandWritePage(BlockIndex, PageIndex, Buffer, SpareBuffer);
      if (EFI_ERROR(Status)) {
        return Status;
      }
      Buffer = ((UINT8 *)Buffer + gNandFlashInfo->PageSize);
    }
  }

  return Status;
}

EFI_STATUS
EFIAPI
NandFlashReset (
  IN EFI_BLOCK_IO_PROTOCOL          *This,
  IN BOOLEAN                        ExtendedVerification
  )
{
  UINTN BusyStall = 50;                            // microSeconds
  UINTN ResetBusyTimeout = (1000000 / BusyStall);  // 1 Second

  //Send RESET command to device.
  NandSendCommand(RESET_CMD);

  //Wait for 1ms before we check status register.
  gBS->Stall(1000);

  //Check BIT#5 & BIT#6 in Status register to make sure RESET is done.
  while ((NandReadStatus() & NAND_RESET_STATUS) != NAND_RESET_STATUS) {

    //In case of extended verification, wait for extended amount of time
    //to make sure device is reset.
    if (ExtendedVerification) {
      if (ResetBusyTimeout == 0) {
        return EFI_DEVICE_ERROR;
      }

      gBS->Stall(BusyStall);
      ResetBusyTimeout--;
    }
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
NandFlashReadBlocks (
  IN EFI_BLOCK_IO_PROTOCOL          *This,
  IN UINT32                         MediaId,
  IN EFI_LBA                        Lba,
  IN UINTN                          BufferSize,
  OUT VOID                          *Buffer
  )
{
  UINTN      NumBlocks;
  UINTN      EndBlockIndex;
  EFI_STATUS Status;
  UINT8      *SpareBuffer = NULL;

  if (Buffer == NULL) {
    Status = EFI_INVALID_PARAMETER;
    goto exit;
  }

  if (Lba > LAST_BLOCK) {
    Status = EFI_INVALID_PARAMETER;
    goto exit;
  }

  if ((BufferSize % gNandFlashInfo->BlockSize) != 0) {
    Status = EFI_BAD_BUFFER_SIZE;
    goto exit;
  }

  NumBlocks = DivU64x32(BufferSize, gNandFlashInfo->BlockSize);
  EndBlockIndex = ((UINTN)Lba + NumBlocks) - 1;

  SpareBuffer = (UINT8 *)AllocatePool(gNandFlashInfo->SparePageSize);
  if (SpareBuffer == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto exit;
  }

  //Read block
  Status = NandReadBlock((UINTN)Lba, EndBlockIndex, Buffer, SpareBuffer);
  if (EFI_ERROR(Status)) {
    DEBUG((EFI_D_ERROR, "Read block fails: %x\n", Status));
    goto exit;
  }

exit:
  if (SpareBuffer != NULL) {
    FreePool (SpareBuffer);
  }

  return Status;
}

EFI_STATUS
EFIAPI
NandFlashWriteBlocks (
  IN EFI_BLOCK_IO_PROTOCOL          *This,
  IN UINT32                         MediaId,
  IN EFI_LBA                        Lba,
  IN UINTN                          BufferSize,
  IN VOID                           *Buffer
  )
{
  UINTN      BlockIndex;
  UINTN      NumBlocks;
  UINTN      EndBlockIndex;
  EFI_STATUS Status;
  UINT8      *SpareBuffer = NULL;

  if (Buffer == NULL) {
    Status = EFI_INVALID_PARAMETER;
    goto exit;
  }

  if (Lba > LAST_BLOCK) {
    Status = EFI_INVALID_PARAMETER;
    goto exit;
  }

  if ((BufferSize % gNandFlashInfo->BlockSize) != 0) {
    Status = EFI_BAD_BUFFER_SIZE;
    goto exit;
  }

  NumBlocks = DivU64x32(BufferSize, gNandFlashInfo->BlockSize);
  EndBlockIndex = ((UINTN)Lba + NumBlocks) - 1;

  SpareBuffer = (UINT8 *)AllocatePool(gNandFlashInfo->SparePageSize);
  if (SpareBuffer == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto exit;
  }

  // Erase block
  for (BlockIndex = (UINTN)Lba; BlockIndex <= EndBlockIndex; BlockIndex++) {
    Status = NandEraseBlock(BlockIndex);
    if (EFI_ERROR(Status)) {
      DEBUG((EFI_D_ERROR, "Erase block failed. Status: %x\n", Status));
      goto exit;
    }
  }

  // Program data
  Status = NandWriteBlock((UINTN)Lba, EndBlockIndex, Buffer, SpareBuffer);
  if (EFI_ERROR(Status)) {
    DEBUG((EFI_D_ERROR, "Block write fails: %x\n", Status));
    goto exit;
  }

exit:
  if (SpareBuffer != NULL) {
    FreePool (SpareBuffer);
  }

  return Status;
}

EFI_STATUS
EFIAPI
NandFlashFlushBlocks (
  IN EFI_BLOCK_IO_PROTOCOL  *This
  )
{
  return EFI_SUCCESS;
}



EFI_BLOCK_IO_MEDIA gNandFlashMedia = {
  SIGNATURE_32('n','a','n','d'),            // MediaId
  FALSE,                                    // RemovableMedia
  TRUE,                                     // MediaPresent
  FALSE,                                    // LogicalPartition
  FALSE,                                    // ReadOnly
  FALSE,                                    // WriteCaching
  0,                                        // BlockSize
  2,                                        // IoAlign
  0,                                        // Pad
  0                                         // LastBlock
};

EFI_BLOCK_IO_PROTOCOL BlockIo =
{
  EFI_BLOCK_IO_INTERFACE_REVISION,  // Revision
  &gNandFlashMedia,                  // *Media
  NandFlashReset,                   // Reset
  NandFlashReadBlocks,              // ReadBlocks
  NandFlashWriteBlocks,             // WriteBlocks
  NandFlashFlushBlocks              // FlushBlocks
};

EFI_STATUS
NandFlashInitialize (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS  Status;

  gNandFlashInfo = (NAND_FLASH_INFO *)AllocateZeroPool (sizeof(NAND_FLASH_INFO));

  //Initialize GPMC module.
  GpmcInit();

  //Reset NAND part
  NandFlashReset(&BlockIo, FALSE);

  //Detect NAND part and populate gNandFlashInfo structure
  Status = NandDetectPart ();
  if (EFI_ERROR(Status)) {
    DEBUG((EFI_D_ERROR, "Nand part id detection failure: Status: %x\n", Status));
    return Status;
  }

  //Count total number of 512Bytes chunk based on the page size.
  if (gNandFlashInfo->PageSize == PAGE_SIZE_512B) {
    gNum512BytesChunks = 1;
  } else if (gNandFlashInfo->PageSize == PAGE_SIZE_2K) {
    gNum512BytesChunks = 4;
  } else if (gNandFlashInfo->PageSize == PAGE_SIZE_4K) {
    gNum512BytesChunks = 8;
  }

  gEccCode = (UINT8 *)AllocatePool(gNum512BytesChunks * 3);
  if (gEccCode == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //Configure ECC
  NandConfigureEcc ();

  //Patch EFI_BLOCK_IO_MEDIA structure.
  gNandFlashMedia.BlockSize = gNandFlashInfo->BlockSize;
  gNandFlashMedia.LastBlock = LAST_BLOCK;

  //Publish BlockIO.
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &ImageHandle,
                  &gEfiBlockIoProtocolGuid, &BlockIo,
                  &gEfiDevicePathProtocolGuid, &gDevicePath,
                  NULL
                  );
  return Status;
}

